// PART OF THE MACHINE SIMULATION. DO NOT CHANGE.

package nachos.machine;

import java.io.EOFException;

/**
 * A COFF (common object file format) loader.
 */
public class Coff {
    /**
     * Allocate a new Coff object.
     */
    protected Coff() {
        file = null;
        entryPoint = 0;
        sections = null;
    }

    /**
     * Load the COFF executable in the specified file.
     *
     * <p>
     * Notes:
     * <ol>
     * <li>If the constructor returns successfully, the file becomes the
     * property of this loader, and should not be accessed any further.
     * <li>The autograder expects this loader class to be used. Do not load
     * sections through any other mechanism.
     * <li>This loader will verify that the file is backed by a file system,
     * by asserting that read() operations take non-zero simulated time to
     * complete. Do not supply a file backed by a simulated cache (the primary
     * purpose of this restriction is to prevent sections from being loaded
     * instantaneously while handling page faults).
     * </ol>
     *
     * @param    file    the file containing the executable.
     * @exception EOFException    if the executable is corrupt.
     */
    public Coff(OpenFile file) throws EOFException {
        this.file = file;

        Coff coff = Machine.autoGrader().createLoader(file);

        if (coff != null) {
            this.entryPoint = coff.entryPoint;
            this.sections = coff.sections;
        } else {
            byte[] headers = new byte[headerLength + aoutHeaderLength];

            if (file.length() < headers.length) {
                Lib.debug(dbgCoff, "\tfile is not executable");
                throw new EOFException();
            }

            Lib.strictReadFile(file, 0, headers, 0, headers.length);

            int magic = Lib.bytesToUnsignedShort(headers, 0);
            int numSections = Lib.bytesToUnsignedShort(headers, 2);
            int optionalHeaderLength = Lib.bytesToUnsignedShort(headers, 16);
            int flags = Lib.bytesToUnsignedShort(headers, 18);
            entryPoint = Lib.bytesToInt(headers, headerLength + 16);

            if (magic != 0x0162) {
                Lib.debug(dbgCoff, "\tincorrect magic number");
                throw new EOFException();
            }
            if (numSections < 2 || numSections > 10) {
                Lib.debug(dbgCoff, "\tbad section count");
                throw new EOFException();
            }
            if ((flags & 0x0003) != 0x0003) {
                Lib.debug(dbgCoff, "\tbad header flags");
                throw new EOFException();
            }

            int offset = headerLength + optionalHeaderLength;

            sections = new CoffSection[numSections];
            for (int s = 0; s < numSections; s++) {
                int sectionEntryOffset = offset + s * CoffSection.headerLength;
                try {
                    sections[s] =
                            new CoffSection(file, this, sectionEntryOffset);
                } catch (EOFException e) {
                    Lib.debug(dbgCoff, "\terror loading section " + s);
                    throw e;
                }
            }
        }
    }

    /**
     * Return the number of sections in the executable.
     *
     * @return the number of sections in the executable.
     */
    public int getNumSections() {
        return sections.length;
    }

    /**
     * Return an object that can be used to access the specified section. Valid
     * section numbers include <tt>0</tt> through <tt>getNumSections() -
     * 1</tt>.
     *
     * @param    sectionNumber    the section to select.
     * @return an object that can be used to access the specified section.
     */
    public CoffSection getSection(int sectionNumber) {
        Lib.assertTrue(sectionNumber >= 0 && sectionNumber < sections.length);

        return sections[sectionNumber];
    }

    /**
     * Return the program entry point. This is the value that to which the PC
     * register should be initialized to before running the program.
     *
     * @return the program entry point.
     */
    public int getEntryPoint() {
        Lib.assertTrue(file != null);

        return entryPoint;
    }

    /**
     * Close the executable file and release any resources allocated by this
     * loader.
     */
    public void close() {
        file.close();

        sections = null;
    }

    private final OpenFile file;

    /**
     * The virtual address of the first instruction of the program.
     */
    protected int entryPoint;
    /**
     * The sections in this COFF executable.
     */
    protected CoffSection[] sections;

    private static final int headerLength = 20;
    private static final int aoutHeaderLength = 28;

    private static final char dbgCoff = 'c';
}
